﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using TFDP.Common;
using TFDP.VsPackage.Extenders;
using TFDP.VsPackage.Support;
using System.IO;
using TFDP.VsPackage.Forms;
using TFDP.VsPackage.Services;
using TFDP.VisualStudioInterop;

namespace TFDP.VsPackage
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidTFDP_PackagePkgString)]
    [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
    [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionOpening_string)]
    [ProvideToolWindow(typeof(QueueMonitor), Orientation = ToolWindowOrientation.Left)]
    public sealed class TFDPPackage : Package
    {
        private ProjectNotifierService notifierService;

        #region Constructors
        public TFDPPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }
        #endregion

        #region Package Members
        protected override void Initialize()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                // Create the command for the query status menu item.
                CommandID regenerateCommandID = new CommandID(GuidList.guidTFDP_SolutionExplorerCmdSet, (int)PkgCmdIDList.cmdidRegenerate);
                OleMenuCommand regenerateMenuCommand = new OleMenuCommand(RegenerateItemCallback, null, RegenerateItemCallback_BeforeQueryStatus, regenerateCommandID, "Regenerate");
                mcs.AddCommand(regenerateMenuCommand);

                // Create the command for the query status menu item.
                CommandID regenerateFolderCommandID = new CommandID(GuidList.guidTFDP_SolutionExplorerCmdSetFolder, (int)PkgCmdIDList.cmdidRegenerateFolder);
                OleMenuCommand regenerateFolderMenuCommand = new OleMenuCommand(RegenerateItemCallback, null, RegenerateItemCallback_BeforeQueryStatus, regenerateFolderCommandID, "Regenerate");
                mcs.AddCommand(regenerateFolderMenuCommand);

                // Create the command for the show queue menu item
                CommandID showQueueCommandID = new CommandID(GuidList.guidTFDP_ShowQueueCommand, (int)PkgCmdIDList.cmdidShowQueue);
                OleMenuCommand showQueueMenuCommand = new OleMenuCommand(ShowQueueCallback, null, ShowQueueCallback_BeforeQueryStatus, showQueueCommandID, "Show Generation Queue");
                mcs.AddCommand(showQueueMenuCommand);
            }

            notifierService = new ProjectNotifierService();
            ((IServiceContainer)this).AddService(typeof(ProjectNotifierService), notifierService);
            ((IServiceContainer)this).AddService(typeof(TemplateManagerService), new TemplateManagerService(null));
            
            InitializeRunningDocumentTableEvents();
        }

        private void InitializeRunningDocumentTableEvents()
        {
            IVsRunningDocumentTable rdt = this.GetService<IVsRunningDocumentTable, SVsRunningDocumentTable>();
            
            ((IServiceContainer)this).AddService(typeof(IFileChangeService), new FileChangeService(rdt));

            uint rdtCookie;
            RdtEventSink rdtEventSink = new RdtEventSink(rdt);

            rdt.AdviseRunningDocTableEvents(rdtEventSink, out rdtCookie);

            rdtEventSink.RdtCookie = rdtCookie;
            rdtEventSink.RegenerateTemplate += new EventHandler<RegenerateTemplateEventArgs>(rdtEventSink_RegenerateTemplate);
        }

        private void rdtEventSink_RegenerateTemplate(object sender, RegenerateTemplateEventArgs e)
        {
            IVsHierarchy hierarchy = e.Hierarchy;

            if (hierarchy != null)
            {
                IDatabaseNode node = VsInteropUtilities.GetNode(hierarchy);
                if (node is IDatabaseFileNode)
                {
                    bool isTemplate = (Path.GetExtension(e.FileName).ToLower() == ".tt");

                    bool skipAutogenerate = false;
                    string val = String.Empty;
                    node.GetItemAttribute(ExtendedType.SkipAutogenerationProperty, out val);
                    Boolean.TryParse(val, out skipAutogenerate);

                    if(isTemplate && !skipAutogenerate)
                        RegenerateFromHierarchy(hierarchy);
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
        #endregion

        #region Menu Item Callbacks
        private void ShowQueueCallback(object sender, EventArgs e)
        {
            ToolWindowPane pane = this.FindToolWindow(typeof(QueueMonitor), 0, true);
            if (pane != null)
            {
                IVsWindowFrame frame = pane.Frame as IVsWindowFrame;
                if (frame != null)
                    frame.Show();
            }
        }

        private void ShowQueueCallback_BeforeQueryStatus(object sender, EventArgs e)
        {

        }

        private void RegenerateItemCallback(object sender, EventArgs e)
        {
            OleMenuCommand command = sender as OleMenuCommand;

            if (command == null)
                return;

            IntPtr hierarchyPtr, selectionContainerPtr;
            uint projectItemId;
            IVsMultiItemSelect mis;
            IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));
            monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);

            if (hierarchyPtr != IntPtr.Zero)
            {
                IVsHierarchy hierarchy = Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;

                RegenerateFromHierarchy(hierarchy);
            }
        }

        private static void RegenerateFromHierarchy(IVsHierarchy hierarchy)
        {
            if (hierarchy != null)
            {
                IDatabaseNode node = VsInteropUtilities.GetNode(hierarchy);

                if (node is IDatabaseFileNode && !node.ItemProperty(CommonProperties.IsGeneratedProperty))
                {
                    // Queue item for processing
                    ((RegenerateObjectService)node.ProjectManager.GetService(typeof(RegenerateObjectService))).Regenerate(node);
                }
                else if (node is IDatabaseFolderNode)
                {
                    IDatabaseFolderNode folder = node as IDatabaseFolderNode;

                    RegenerateObjectService svc = ((RegenerateObjectService)node.ProjectManager.GetService(typeof(RegenerateObjectService)));

                    foreach (IDatabaseNode child in folder.Children)
                    {
                        if (!(child is IDatabaseFileNode))
                            continue;

                        if (child.ItemProperty(CommonProperties.IsGeneratedProperty))
                            continue;
                        else
                            svc.Regenerate(child);
                    }
                }
            }
        }

        private void RegenerateItemCallback_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommand command = sender as OleMenuCommand;

            if (command == null)
                return;

            IntPtr hierarchyPtr, selectionContainerPtr;
            uint projectItemId;
            IVsMultiItemSelect mis;
            IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));
            monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);

            if (hierarchyPtr == IntPtr.Zero)
                return;

            IVsHierarchy hierarchy = Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;

            if (hierarchy != null)
            {
                IDatabaseNode node = VsInteropUtilities.GetNode(hierarchy);

                if (node is IDatabaseFileNode)
                {
                    if (node.ItemProperty(CommonProperties.IsGeneratedProperty))
                    {
                        command.Visible = false;
                        command.Enabled = false;
                    }
                    else
                    {
                        command.Visible = true;
                        command.Enabled = true;
                    }
                }
                else if (node is IDatabaseFolderNode)
                {
                    command.Visible = true;
                    command.Enabled = true;
                }
                else
                {
                    command.Visible = false;
                    command.Enabled = false;
                }
            }
        }
        #endregion

        #region AddIn Supports
        /// <summary>
        /// Obtain a service from the VisualStudio application.
        /// </summary>
        /// <typeparam name="T">The return type to expect</typeparam>
        /// <typeparam name="S">The service interface type</typeparam>
        /// <returns>The requested service</returns>
        private T GetService<T, S>()
        {
            return (T)GetService(typeof(S));
        }
        #endregion

        #region Internal Event Handlers
        private void errorProvider_NavigationRequest(object sender, NavigationRequestEventArgs e)
        {
            EnvDTE.DTE dte = GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;

            dte.ItemOperations.OpenFile(e.Document);
            EnvDTE.TextSelection selection = dte.ActiveDocument.Selection as EnvDTE.TextSelection;

            selection.MoveToLineAndOffset(e.Line, e.Column);
        }
        #endregion
    }

    internal class RdtEventSink : IVsRunningDocTableEvents
    {
        private IVsRunningDocumentTable rdt;

        internal uint RdtCookie { get; set; }

        public RdtEventSink(IVsRunningDocumentTable rdt)
        {
            this.rdt = rdt;
        }

        public int OnAfterSave(uint docCookie)
        {
            uint pgrfRDTFlags;
            uint pdwReadLocks;
            uint pdwEditLocks;
            string pbstrMkDocument;
            IVsHierarchy ppHier;
            uint pitemid;
            IntPtr ppunkDocData;

            // Resolve document filename from docCookie
            rdt.GetDocumentInfo(
                docCookie,
                out pgrfRDTFlags,
                out pdwReadLocks,
                out pdwEditLocks,
                out pbstrMkDocument,
                out ppHier,
                out pitemid,
                out ppunkDocData
            );

            // Raise RegenerateTemplate event
            OnRegenerateTemplate(pbstrMkDocument, ppHier);

            // Finished
            return VSConstants.S_OK;
        }

        public event EventHandler<RegenerateTemplateEventArgs> RegenerateTemplate;

        private void OnRegenerateTemplate(string fileName, IVsHierarchy hierarchy)
        {
            if (RegenerateTemplate != null)
            {
                RegenerateTemplate(this, new RegenerateTemplateEventArgs() { FileName = fileName, Hierarchy = hierarchy });
            }
        }

        #region Ignore
        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }
        #endregion
    }
}
